/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#ifdef CONFIG_ENABLE_SMP
#include <plat/config.h>
#include <arch/utils.h>
#include <arch/cortex-a55/cpu.h>
#include <hal/interrupt_ctrl.h>
#include <core/schedule.h>
#include <core/smp.h>
#include <core/interrupt.h>
#include <arch/ipi.h>
#include <core/schedule.h>
#include <core/task.h>
#include <core/timer.h>

ipi_data_t    ipi_datas[CONFIG_SMP_CORES];
extern void   ipi_send_target(uint32_t irq, word_t cpuTargetList);
extern word_t mpidr_map[CONFIG_SMP_CORES];

static void delay_time(void)
{
    for (int i = 120; i > 0; i--) {
        for (int j = 65535; j > 0; j--) {
            ;
        }
    }
}

static void ipi_run_idle_handler(void)
{
    schedule_action_change(ACTION_RUN_IDLE);
    schedule();
}

static void ipi_re_schedule_handler(void)
{
    schedule_action_change(ACTION_CHOOSE_NEW);
    schedule();
}

static void ipi_task_kill_handler(void)
{
    unsigned int core = current_cpu();
    tcb_t       *task;
    ipi_data_t  *ipi_data = ipi_datas + core;

    task            = ipi_data->task;
    ipi_data->task  = IPI_TASK_EMPTY;
    ipi_data->state = IPIDATA_NOT_SET;

    spin_unlock(&ipi_data->lock);

    schedule_detach(task);

    schedule();
}

static void ipi_task_out_handler(void)
{
    unsigned int core = current_cpu();
    unsigned int targetcpu;
    tcb_t       *task;
    ipi_data_t  *ipi_data = ipi_datas + core;

    targetcpu = ipi_data->target_cpu;
    task      = ipi_data->task;

    ipi_data->task       = IPI_TASK_EMPTY;
    ipi_data->target_cpu = 0;
    ipi_data->state      = IPIDATA_NOT_SET;

    spin_unlock(&ipi_data->lock);

    ipi_data = ipi_datas + targetcpu;

    while (ipi_data->state != IPIDATA_NOT_SET) {
        ;
    }
    spin_lock(&ipi_data->lock);

    tcb_get(task);
    schedule_detach(task);
    ipi_data->task  = task;
    ipi_data->state = IPIDATA_TASK_IN;

    spin_unlock(&ipi_data->lock);

    ipi_send_target(IPI_ICC, bitmask(targetcpu));
    schedule();
}

static void ipi_task_in_handler(void)
{
    unsigned int core = current_cpu();
    tcb_t       *task;
    ipi_data_t  *ipi_data = ipi_datas + core;
    task                  = ipi_data->task;

    ipi_data->task  = IPI_TASK_EMPTY;
    ipi_data->state = IPIDATA_NOT_SET;
    spin_unlock(&ipi_data->lock);

    list_remove(&task->runlist);
    task_set_readytime(task, timer_current());
    task_set_state(task, TASK_READY);
    task_set_affinity(task, core);
    schedule_accept_task(task);

    schedule();
}

static void ipi_call_func_handler(void)
{
    unsigned int core     = current_cpu();
    ipi_data_t  *ipi_data = ipi_datas + core;

    handler_t handler = (handler_t)(ipi_data->handler);
    ipi_data->handler = ((handler_t)0);
    ipi_data->state   = IPIDATA_NOT_SET;

    spin_unlock(&ipi_data->lock);
    handler();
}

static void ipi_task_ipc_handler(void)
{
    unsigned int core     = current_cpu();
    ipi_data_t  *ipi_data = ipi_datas + core;

    ipi_data->msg   = (void *)0;
    ipi_data->state = IPIDATA_NOT_SET;

    spin_unlock(&ipi_data->lock);
}

static void ipi_unvalid_handler(void)
{
    unsigned int core     = current_cpu();
    ipi_data_t  *ipi_data = ipi_datas + core;

    ipi_data->state = IPIDATA_NOT_SET;
    spin_unlock(&ipi_data->lock);
}

static void ipi_icc_handler(void)
{
    unsigned int core     = current_cpu();
    ipi_data_t  *ipi_data = ipi_datas + core;

    switch (ipi_data->state) {
        case IPIDATA_TASK_KILL:
            ipi_task_kill_handler();
            break;
        case IPIDATA_TASK_OUT:
            ipi_task_out_handler();
            break;
        case IPIDATA_TASK_IN:
            ipi_task_in_handler();
            break;
        case IPIDATA_CALL_FUNC:
            ipi_call_func_handler();
            break;
        case IPIDATA_TASK_IPC:
            ipi_task_ipc_handler();
            break;
        default:
            ipi_unvalid_handler();
            break;
    }
}

void ipi_setup_handler(void)
{
    irq_setup_handler(IPI_RUN_IDLE, ipi_run_idle_handler);
    irq_setup_handler(IPI_RE_SCHEDULE, ipi_re_schedule_handler);
    irq_setup_handler(IPI_ICC, ipi_icc_handler);
}

static void icc_test_out_in(int srcCore, int targetCore)
{
    schedule_data_t *sd1      = get_schedule_data((unsigned int)srcCore);
    ipi_data_t      *ipi_data = ipi_datas + srcCore;
    (void)memset(ipi_data, 0, sizeof(ipi_data_t));
    if (list_is_empty(&sd1->readyqueue[62])) {
        dprintf("list is empty!!\n");
        return;
    }
    /* PRQA S 2810 1 */
    ipi_data->task       = container_of(sd1->readyqueue[62].next, tcb_t, runlist);
    ipi_data->state      = IPIDATA_TASK_OUT;
    ipi_data->target_cpu = (unsigned int)targetCore;
    ipi_send_target(IPI_ICC, bitmask(srcCore));
}

static void func(void)
{
    dprintf("!!!!!!!!!!!!!!!!!hello world!!!!!!!!!!!!!!!!!\n");
}

static void icc_test_func(int targetCore)
{
    dprintf("icc_test_func\n");
    ipi_data_t *ipi_data = ipi_datas + targetCore;
    ipi_data->state      = IPIDATA_CALL_FUNC;
    ipi_data->handler    = (handler_t)(func);
    ipi_send_target(IPI_ICC, bitmask(targetCore));
}

static void icc_test_ipc(int targetCore)
{
    dprintf("icc_test_func\n");
    const char *data     = "hello world!";
    ipi_data_t *ipi_data = ipi_datas + targetCore;
    ipi_data->state      = IPIDATA_TASK_IPC;
    ipi_data->msg        = (void *)data;
    ipi_send_target(IPI_ICC, bitmask(targetCore));
}

void ipi_test(void)
{
    ipi_send_target(IPI_RUN_IDLE, bitmask(1));
    delay_time();

    ipi_send_target(IPI_RE_SCHEDULE, bitmask(1));
    delay_time();

    icc_test_out_in(1, 2);
    delay_time();
    delay_time();

    icc_test_func(2);
    delay_time();
    delay_time();

    icc_test_ipc(2);
}
#endif
